{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Create Custom Bounding Box Table\n", "\n", "Build a 3LC Table from scratch with custom bounding box annotations and schema definitions for specialized object detection scenarios.\n", "\n", "![img](../../images/create-bb-table.jpg)\n", "\n", "\n", "\n", "While COCO and YOLO formats cover most use cases, specialized applications may require custom schemas, additional metadata, or non-standard annotation formats. Custom tables give you complete control over data structure and validation.\n", "\n", "This notebook provides a step-by-step guide to defining custom table schemas and formatting bounding box data for 3LC. We demonstrate manual schema creation, data validation, and proper formatting of coordinate systems and class labels. Use this approach when working with proprietary annotation formats, specialized coordinate systems, or when you need additional metadata that standard formats don't support." ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "## Install dependencies" ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [], "source": [ "%pip install 3lc" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "4", "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "import tlc\n", "from PIL import Image" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "## Project setup" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": { "tags": [ "parameters" ] }, "outputs": [], "source": [ "DATA_PATH = \"../../../data\"\n", "PROJECT_NAME = \"3LC Tutorials - Cats & Dogs\"" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "cats_and_dogs_folder = Path(DATA_PATH) / \"cats-and-dogs\"\n", "assert cats_and_dogs_folder.exists()" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "## Setup the TableWriter\n", "\n", "First, we need to import the `tlc` library and create a `tlc.TableWriter` object.\n", "We will provide a `tlc.Schema` to the table writer in a later cell." ] }, { "cell_type": "markdown", "id": "9", "metadata": {}, "source": [ "A column of bounding boxes in 3LC is represented as a dictionary of the form:\n", "\n", "```python\n", "{\n", " \"image_width\": float,\n", " \"image_height\": float,\n", " \"bb_list\": [\n", " {\n", " \"x0\": float, # First \"horizontal\" coordinate\n", " \"x1\": float, # Second \"horizontal\" coordinate\n", " \"y0\": float, # First \"vertical\" coordinate\n", " \"y1\": float, # Second \"vertical\" coordinate\n", " \"label\": str # Label of the bounding box\n", " },\n", " ...\n", " ]\n", "}\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": [ "bb_schema = tlc.BoundingBoxListSchema(\n", " label_value_map={0.0: tlc.MapElement(\"dog\"), 1.0: tlc.MapElement(\"cat\")},\n", " x0_number_role=tlc.NUMBER_ROLE_BB_CENTER_X,\n", " y0_number_role=tlc.NUMBER_ROLE_BB_CENTER_Y,\n", " x1_number_role=tlc.NUMBER_ROLE_BB_SIZE_X,\n", " y1_number_role=tlc.NUMBER_ROLE_BB_SIZE_Y,\n", " x0_unit=\"relative\",\n", " y0_unit=\"relative\",\n", " x1_unit=\"relative\",\n", " y1_unit=\"relative\",\n", " include_segmentation=False,\n", ")\n", "\n", "schemas = {\n", " \"image\": tlc.ImageUrlSchema(),\n", " \"bounding_boxes\": bb_schema,\n", "}\n", "\n", "table_writer = tlc.TableWriter(\n", " project_name=\"3LC Tutorials - Create Tables\",\n", " dataset_name=\"cats-and-dogs\",\n", " table_name=\"initial-bbs\",\n", " column_schemas=schemas,\n", ")" ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "## Create Table data" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "Let's load the data to populate the `tlc.Table` with. We have a dictionary with a mapping from image to it's bounding boxes.\n", "\n", "The labels are all in XcYcWH relative format, the one specified in the schema. This means each bounding box is defined by its:\n", "\n", " Xc: The x coordinate of the center of the box,\n", " Yc: The y coordinate of the center of the box,\n", " W: The width of the bounding box,\n", " H: The height of the bounding box,\n", " C: The category index of the bounding box - here 0 means dog and 1 means cat\n", "\n", "The coordinates are between 0 and 1, i.e. relative to the image width and height." ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "# Each image has a list of bounding boxes\n", "data = {\n", " \"cats/1500.jpg\": [[0.527, 0.529, 0.941, 0.938, 1]],\n", " \"cats/1501.jpg\": [[0.470, 0.543, 0.866, 0.829, 1]],\n", " \"cats/1502.jpg\": [[0.520, 0.537, 0.705, 0.708, 1]],\n", " \"cats/1503.jpg\": [[0.591, 0.501, 0.814, 0.992, 1]],\n", " \"cats/1504.jpg\": [[0.487, 0.437, 0.819, 0.790, 1]],\n", " \"dogs/1500.jpg\": [[0.496, 0.495, 0.948, 0.897, 0]],\n", " \"dogs/1501.jpg\": [[0.484, 0.493, 0.308, 0.923, 0]],\n", " \"dogs/1502.jpg\": [[0.531, 0.652, 0.487, 0.688, 0]],\n", " \"dogs/1503.jpg\": [[0.520, 0.504, 0.945, 0.968, 0]],\n", " \"dogs/1504.jpg\": [[0.530, 0.497, 0.929, 0.944, 0]],\n", "}" ] }, { "cell_type": "markdown", "id": "14", "metadata": {}, "source": [ "When populating the `tlc.Table`, we need to convert these boxes to appropriately formatted dictionaries." ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": {}, "outputs": [], "source": [ "table_rows = {\"image\": [], \"bounding_boxes\": []}\n", "\n", "for relative_image_path, bbs in data.items():\n", " # Prepare full image path\n", " image_path = cats_and_dogs_folder / relative_image_path\n", "\n", " # Prepare bounding boxes\n", " image = Image.open(image_path)\n", " image_width, image_height = image.size\n", " bb_list = [{\"x0\": bb[0], \"y0\": bb[1], \"x1\": bb[2], \"y1\": bb[3], \"label\": bb[4]} for bb in bbs]\n", " boxes = {\"image_height\": image_height, \"image_width\": image_width, \"bb_list\": bb_list}\n", "\n", " # Populate table rows\n", " table_rows[\"image\"].append(tlc.Url(image_path).to_relative().to_str())\n", " table_rows[\"bounding_boxes\"].append(boxes)" ] }, { "cell_type": "code", "execution_count": null, "id": "16", "metadata": {}, "outputs": [], "source": [ "table_writer.add_batch(table_rows)\n", "table = table_writer.finalize()" ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": {}, "outputs": [], "source": [ "print(table)" ] }, { "cell_type": "markdown", "id": "18", "metadata": {}, "source": [ "## Inspect the data" ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": {}, "outputs": [], "source": [ "# Inspect the first row\n", "table[0]" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.9" } }, "nbformat": 4, "nbformat_minor": 5 }